/*
 * Copyright 2013 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package io.netty.util;

import io.netty.util.internal.PlatformDependent;
import java.io.InputStream;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;

/**
 * Retrieves the version information of available Netty artifacts.
 * <p>
 * This class retrieves the version information from {@code META-INF/io.netty.versions.properties},
 * which is generated in build time.  Note that it may not be possible to retrieve the information
 * completely, depending on your environment, such as the specified {@link ClassLoader}, the current
 * {@link SecurityManager}.
 * </p>
 */
public final class Version {

  private static final String PROP_VERSION = ".version";
  private static final String PROP_BUILD_DATE = ".buildDate";
  private static final String PROP_COMMIT_DATE = ".commitDate";
  private static final String PROP_SHORT_COMMIT_HASH = ".shortCommitHash";
  private static final String PROP_LONG_COMMIT_HASH = ".longCommitHash";
  private static final String PROP_REPO_STATUS = ".repoStatus";

  /**
   * Retrieves the version information of Netty artifacts using the current {@linkplain
   * Thread#getContextClassLoader() context class loader}.
   *
   * @return A {@link Map} whose keys are Maven artifact IDs and whose values are {@link Version}s
   */
  public static Map<String, Version> identify() {
    return identify(null);
  }

  /**
   * Retrieves the version information of Netty artifacts using the specified {@link ClassLoader}.
   *
   * @return A {@link Map} whose keys are Maven artifact IDs and whose values are {@link Version}s
   */
  public static Map<String, Version> identify(ClassLoader classLoader) {
    if (classLoader == null) {
      classLoader = PlatformDependent.getContextClassLoader();
    }

    // Collect all properties.
    Properties props = new Properties();
    try {
      Enumeration<URL> resources = classLoader
          .getResources("META-INF/io.netty.versions.properties");
      while (resources.hasMoreElements()) {
        URL url = resources.nextElement();
        InputStream in = url.openStream();
        try {
          props.load(in);
        } finally {
          try {
            in.close();
          } catch (Exception ignore) {
            // Ignore.
          }
        }
      }
    } catch (Exception ignore) {
      // Not critical. Just ignore.
    }

    // Collect all artifactIds.
    Set<String> artifactIds = new HashSet<String>();
    for (Object o : props.keySet()) {
      String k = (String) o;

      int dotIndex = k.indexOf('.');
      if (dotIndex <= 0) {
        continue;
      }

      String artifactId = k.substring(0, dotIndex);

      // Skip the entries without required information.
      if (!props.containsKey(artifactId + PROP_VERSION) ||
          !props.containsKey(artifactId + PROP_BUILD_DATE) ||
          !props.containsKey(artifactId + PROP_COMMIT_DATE) ||
          !props.containsKey(artifactId + PROP_SHORT_COMMIT_HASH) ||
          !props.containsKey(artifactId + PROP_LONG_COMMIT_HASH) ||
          !props.containsKey(artifactId + PROP_REPO_STATUS)) {
        continue;
      }

      artifactIds.add(artifactId);
    }

    Map<String, Version> versions = new TreeMap<String, Version>();
    for (String artifactId : artifactIds) {
      versions.put(
          artifactId,
          new Version(
              artifactId,
              props.getProperty(artifactId + PROP_VERSION),
              parseIso8601(props.getProperty(artifactId + PROP_BUILD_DATE)),
              parseIso8601(props.getProperty(artifactId + PROP_COMMIT_DATE)),
              props.getProperty(artifactId + PROP_SHORT_COMMIT_HASH),
              props.getProperty(artifactId + PROP_LONG_COMMIT_HASH),
              props.getProperty(artifactId + PROP_REPO_STATUS)));
    }

    return versions;
  }

  private static long parseIso8601(String value) {
    try {
      return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse(value).getTime();
    } catch (ParseException ignored) {
      return 0;
    }
  }

  /**
   * Prints the version information to {@link System#err}.
   */
  public static void main(String[] args) {
    for (Version v : identify().values()) {
      System.err.println(v);
    }
  }

  private final String artifactId;
  private final String artifactVersion;
  private final long buildTimeMillis;
  private final long commitTimeMillis;
  private final String shortCommitHash;
  private final String longCommitHash;
  private final String repositoryStatus;

  private Version(
      String artifactId, String artifactVersion,
      long buildTimeMillis, long commitTimeMillis,
      String shortCommitHash, String longCommitHash, String repositoryStatus) {
    this.artifactId = artifactId;
    this.artifactVersion = artifactVersion;
    this.buildTimeMillis = buildTimeMillis;
    this.commitTimeMillis = commitTimeMillis;
    this.shortCommitHash = shortCommitHash;
    this.longCommitHash = longCommitHash;
    this.repositoryStatus = repositoryStatus;
  }

  public String artifactId() {
    return artifactId;
  }

  public String artifactVersion() {
    return artifactVersion;
  }

  public long buildTimeMillis() {
    return buildTimeMillis;
  }

  public long commitTimeMillis() {
    return commitTimeMillis;
  }

  public String shortCommitHash() {
    return shortCommitHash;
  }

  public String longCommitHash() {
    return longCommitHash;
  }

  public String repositoryStatus() {
    return repositoryStatus;
  }

  @Override
  public String toString() {
    return artifactId + '-' + artifactVersion + '.' + shortCommitHash +
        ("clean".equals(repositoryStatus) ? "" : " (repository: " + repositoryStatus + ')');
  }
}
